package com.tencent.compose.sample


import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.drawText
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.*
import kotlin.math.abs
import kotlin.math.floor
import kotlin.math.ln
import kotlin.math.pow
import kotlin.math.round
import kotlin.random.Random
import kotlin.time.TimeSource
import kotlinx.cinterop.ExperimentalForeignApi
import concurrency.runNCoroutinesMixedVerbose
import kotlinx.coroutines.CompletableDeferred

data class Sample(val tMs: Long, val all: Int, val normal: Int, val video: Int)

private const val REQUEST_URL = "http://example.com/"
private const val REQUEST_JITTER_MS = 1_000L
private const val REQUEST_RUN_MS = 60_000L
private const val PERIOD_A_MS = 5_000L
private const val PERIOD_B_MS = 10_000L
private const val PERIOD_C_MS = 20_000L


@OptIn(ExperimentalForeignApi::class)
@Composable
internal fun InteropVideo1(
    onEnded: () -> Unit = {}
) {
    Column(Modifier.fillMaxSize()) {
        ArkUIView(
            name = "video",
            modifier = Modifier
                .fillMaxWidth()
                .weight(1f)
        )
        Row(
            Modifier
                .fillMaxWidth()
                .padding(8.dp),
            horizontalArrangement = Arrangement.End
        ) {
            Button(onClick = onEnded) { Text("关闭") }
        }
    }
}

@Composable
internal fun The300Threads() {
    val scope = rememberCoroutineScope()
    var running by remember { mutableStateOf(false) }
    var progress by remember { mutableStateOf(0) }
    val total = 300

    var okA by remember { mutableStateOf(0) }
    var okB by remember { mutableStateOf(0) }
    var okC by remember { mutableStateOf(0) }
    var failA by remember { mutableStateOf(0) }
    var failB by remember { mutableStateOf(0) }
    var failC by remember { mutableStateOf(0) }


    val logs = remember { mutableStateListOf<String>() }
    val logClock = remember { TimeSource.Monotonic }

    val samples = remember { mutableStateListOf<Sample>() }
    var runStart by remember { mutableStateOf(TimeSource.Monotonic.markNow()) }

    fun pushLog(line: String) {
        val ts = logClock.markNow().elapsedNow().inWholeMilliseconds
        val msg = "[$ts] $line"
        println(msg)
        logs += msg
        if (logs.size > 2000) logs.removeFirst()
    }

    fun pushSample(all: Int, normal: Int, video: Int) {
        val t = runStart.elapsedNow().inWholeMilliseconds
        samples += Sample(t, all, normal, video)
        if (samples.size > 4000) samples.removeFirst()
    }

    var showInterop by remember { mutableStateOf(false) }
    var interopSignal by remember { mutableStateOf<CompletableDeferred<Unit>?>(null) }

    val awaitInterop: suspend () -> Unit = {
        withContext(Dispatchers.Main) {
            interopSignal = CompletableDeferred()
            showInterop = true
        }
        interopSignal!!.await()
    }

    Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
        Column(
            horizontalAlignment = Alignment.CenterHorizontally,
            modifier = Modifier.fillMaxSize().padding(16.dp)
        ) {
            Row(verticalAlignment = Alignment.CenterVertically) {
                Button(
                    enabled = !running,
                    onClick = {
                        running = true
                        progress = 0
                        logs.clear()
                        samples.clear()
                        runStart = TimeSource.Monotonic.markNow()

                        scope.launch {
                            val totalTimer = TimeSource.Monotonic.markNow()
                            pushLog("LAUNCH mixed tasks: 299 normal + 1 video")
                            val specialIndex = total - 1

                            val normalIds = (0 until total).asSequence().filter { it != specialIndex }.toList()

                            val rng = Random.Default

                            val pickCount = minOf(100, normalIds.size)
                            val shuffled = normalIds.shuffled(rng).take(pickCount)

                            val urls = listOf(
                                "http://example.com/",
                                "http://example.org/",
                                "http://example.net/",
                                "http://info.cern.ch/",
                                "http://detectportal.firefox.com/success.txt"
                            )


                            val wantA = 30
                            val wantB = 30
                            val wantC = 40

                            val sizeA = minOf(wantA, pickCount)
                            val sizeB = minOf(wantB, pickCount - sizeA)
                            val sizeC = pickCount - sizeA - sizeB

                            val groupA = shuffled.take(sizeA).toSet()
                            val groupB = shuffled.drop(sizeA).take(sizeB).toSet()
                            val groupC = shuffled.drop(sizeA + sizeB).take(sizeC).toSet()

                            pushLog("REQUEST sets (random): A=${groupA.size} B=${groupB.size} C=${groupC.size} / totalPick=$pickCount")

                            runNCoroutinesMixedVerbose(
                                total = total,
                                specialIndex = specialIndex,
                                onProgress = { v -> withContext(Dispatchers.Main) { progress = v } },
                                onLog = { line -> withContext(Dispatchers.Main) { pushLog(line) } },
                                onActiveChange = { all, normal, video ->
                                    withContext(Dispatchers.Main) { pushSample(all, normal, video) }
                                },
                                doNormalWork = { simulateNormalWork() },
                                doVideoWork  = { simulateVideoWork(awaitInterop) },

                                requestUrl = REQUEST_URL,
                                requestGroupA = groupA,
                                requestGroupB = groupB,
                                requestGroupC = groupC,
                                periodAms = PERIOD_A_MS,
                                periodBms = PERIOD_B_MS,
                                periodCms = PERIOD_C_MS,
                                jitterMs   = REQUEST_JITTER_MS,
                                requestRunMs = REQUEST_RUN_MS,
                                        onRequestResult = { _, group, _, ok, code, err ->
                                    withContext(Dispatchers.Main) {
                                        if (ok) {
                                            when (group) {
                                                'A' -> okA++
                                                'B' -> okB++
                                                'C' -> okC++
                                            }
                                        } else {
                                            when (group) {
                                                'A' -> failA++
                                                'B' -> failB++
                                                'C' -> failC++
                                            }
                                        }
                                    }
                                } ,
                                requestUrls = urls
                            )

                            val cost = totalTimer.elapsedNow().inWholeMilliseconds
                            pushLog("ALL DONE in ${cost}ms")
                            running = false
                        }
                    }
                ) { Text(if (running) "运行中…" else "Run 300 Mixed Tasks") }

                Spacer(Modifier.width(12.dp))
                Button(enabled = !running, onClick = { logs.clear(); samples.clear() }) { Text("清空") }
            }

            Spacer(Modifier.height(8.dp))
            Text("进度：$progress / $total", fontWeight = FontWeight.Medium)
            Spacer(Modifier.height(12.dp))

            Spacer(Modifier.height(8.dp))
            Text(
                "A: ${okA}✅ / ${failA}❌    " +
                        "B: ${okB}✅ / ${failB}❌    " +
                        "C: ${okC}✅ / ${failC}❌",
                fontWeight = FontWeight.Medium
            )
            Spacer(Modifier.height(12.dp))

            ConcurrencyChart(
                samples = samples.toList(),
                modifier = Modifier
                    .fillMaxWidth()
                    .height(200.dp)
                    .border(1.dp, Color(0x22000000))
                    .background(Color(0xFFF9FAFB))
                    .padding(8.dp)
            )

            Spacer(Modifier.height(12.dp))

            val scroll = rememberScrollState()
            LaunchedEffect(logs.size) { scroll.animateScrollTo(scroll.maxValue) }
            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .weight(1f)
                    .verticalScroll(scroll)
                    .padding(8.dp)
                    .background(Color(0xFFF3F4F6))
            ) { Text(logs.joinToString("\n")) }
        }
        if (showInterop) {
            Box(
                Modifier
                    .fillMaxSize()
                    .background(Color(0x66000000)),
                contentAlignment = Alignment.Center
            ) {
                Box(
                    Modifier
                        .fillMaxWidth()
                        .height(320.dp)
                        .border(1.dp, Color(0x22000000))
                        .background(Color.White)
                        .padding(8.dp)
                ) {
                    InteropVideo1(
                        onEnded = {
                            interopSignal?.takeIf { it.isActive }?.complete(Unit)
                            showInterop = false
                        }
                    )
                }
            }
        }

    }
}

private suspend fun simulateNormalWork() {
    delay(Random.nextLong(1000, 5000))
}

private suspend fun simulateVideoWork(
    awaitInterop: suspend () -> Unit
) {
    awaitInterop()
}

@Composable
private fun ConcurrencyChart(
    samples: List<Sample>,
    modifier: Modifier = Modifier
) {
    if (samples.isEmpty()) {
        Box(modifier, contentAlignment = Alignment.Center) {
            Text("等待任务开始…")
        }
        return
    }

    val maxAll = (samples.maxOfOrNull { it.all } ?: 0).coerceAtLeast(1)
    val t0 = samples.first().tMs
    val timeMax = (samples.last().tMs - t0).coerceAtLeast(1L)
    val xTicks = buildNiceTimeTicks(0.0, timeMax.toDouble(), targetTickCount = 6)
    val textMeasurer = rememberTextMeasurer()
    val tickTextStyle = TextStyle(fontSize = 10.sp, color = Color(0xFF374151))

    Canvas(modifier = modifier) {
        val w = size.width
        val h = size.height
        val padL = 52f
        val padR = 12f
        val padT = 12f
        val padB = 30f
        val chartW = w - padL - padR
        val chartH = h - padT - padB

        fun xAt(tRelMs: Long): Float {
            val ratio = tRelMs / timeMax.toFloat()
            return padL + chartW * ratio.coerceIn(0f, 1f)
        }
        fun yAt(v: Int): Float {
            val ratio = v / maxAll.toFloat()
            return padT + chartH * (1f - ratio)
        }

        val ySteps = 5
        for (i in 0..ySteps) {
            val v = maxAll * i / ySteps
            val y = yAt(v)

            drawLine(
                start = Offset(padL, y),
                end = Offset(padL + chartW, y),
                color = Color(0x22000000),
                strokeWidth = 1f
            )

            val layout = textMeasurer.measure(AnnotatedString(v.toString()), style = tickTextStyle)
            drawText(
                textLayoutResult = layout,
                topLeft = Offset(8f, y - layout.size.height / 2f)
            )
        }

        for (tick in xTicks) {
            val x = xAt(tick.toLong())
            drawLine(
                start = Offset(x, padT),
                end = Offset(x, padT + chartH),
                color = Color(0x22000000),
                strokeWidth = 1f
            )

            val label = formatDurationShort(tick.toLong())
            val layout = textMeasurer.measure(AnnotatedString(label), style = tickTextStyle)
            drawText(
                textLayoutResult = layout,
                topLeft = Offset(x - layout.size.width / 2f, h - layout.size.height - 4f)
            )
        }

        drawLine(
            start = Offset(padL, padT),
            end = Offset(padL, padT + chartH),
            color = Color(0x99000000),
            strokeWidth = 2f
        )
        drawLine(
            start = Offset(padL, padT + chartH),
            end = Offset(padL + chartW, padT + chartH),
            color = Color(0x99000000),
            strokeWidth = 2f
        )

        fun drawSeries(selector: (Sample) -> Int, color: Color, stroke: Float) {
            val path = Path()
            val first = samples.first()
            path.moveTo(xAt(first.tMs - t0), yAt(selector(first)))
            for (i in 1 until samples.size) {
                val s = samples[i]
                path.lineTo(xAt(s.tMs - t0), yAt(selector(s)))
            }
            drawPath(path, color = color, style = Stroke(width = stroke))
        }

        drawSeries({ it.all },    Color(0xFF2563EB), 3f)
        drawSeries({ it.normal }, Color(0xFF16A34A), 2f)
        drawSeries({ it.video },  Color(0xFFDC2626), 2.5f)
    }
}

private fun buildNiceTimeTicks(
    min: Double,
    max: Double,
    targetTickCount: Int = 6
): List<Double> {
    val range = (max - min).coerceAtLeast(1.0)
    val rawStep = range / targetTickCount
    val mag = 10.0.pow(floor(ln(rawStep) / ln(10.0)))
    val candidates = doubleArrayOf(1.0, 2.0, 5.0, 10.0)
    val step = candidates.minByOrNull { abs(rawStep - it * mag) }!! * mag

    val start = floor(min / step) * step
    val end = round(max / step) * step

    val ticks = ArrayList<Double>()
    var v = start
    while (v <= end + step * 0.5) {
        ticks += v
        v += step
    }
    return ticks
}

private fun formatDurationShort(ms: Long): String {
    return if (ms < 1000L) {
        "${ms}ms"
    } else {
        val s = ms / 1000.0
        val rounded = round(s * 10) / 10.0
        "${rounded}s"
    }
}

